/**
 * Copyright 2010, Lars J. Nilsson <http://www.larsan.net> 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.larsan.dconf.jbc;

import java.net.InetAddress;
import java.net.UnknownHostException;

import net.larsan.dconf.DNodeStore;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Cache;

import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;

public class JBCModule extends AbstractModule {
	
	public static final String FORCE_LOCAL_PROP = "net.larsan.dconf.jbc.forceLocal";
	public static final String USER_DOMAIN_PROP = "net.larsan.dconf.jbc.userDomain";
	public static final String BROADCAST_PROP = "net.larsan.dconf.jbc.broadcastAddress";
	public static final String JBC_CONFIG_PROP = "net.larsan.dconf.jbc.config";
	public static final String BIND_ADDRESS_PROP = "net.larsan.dconf.jbc.bindAddress";
	
	private static final String DEF_BROADCAST_ADDRESS = "232.1.2.3";
	private static final String DEF_JBC_CONFIG = "classpath:cacheConfig.xml";
	private static final String DEF_LOCAL_CONFIG = "classpath:localCache.xml";
	
	private final String userDomain;
	private final InetAddress broadcastAddress;
	private final String jbcConfig;
	private final InetAddress bindAddress;
	
	private final Log log = LogFactory.getLog(getClass());
	
	public JBCModule() {
		this(null, null, null, null);
	}
	
	public JBCModule(String userDomain) {
		this(userDomain, null, null, null);
	}
	
	public JBCModule(InetAddress broadcastAddress) {
		this(null, broadcastAddress, null, null);
	}
	
	public JBCModule(String userDomain, InetAddress broadcastAddress) {
		this(userDomain, broadcastAddress, null, null);
	}
	
	public JBCModule(String userDomain, InetAddress broadcastAddress, InetAddress bindAddress) {
		this(userDomain, broadcastAddress, null, bindAddress);
	}
	
	public JBCModule(InetAddress broadcastAddress, InetAddress bindAddress) {
		this(null, broadcastAddress, null, bindAddress);
	}

	public JBCModule(String userDomain, InetAddress broadcastAddress, String jbcConfig, InetAddress bindAddress) {
		this.userDomain = userDomain;
		this.broadcastAddress = broadcastAddress;
		this.jbcConfig = jbcConfig;
		this.bindAddress = bindAddress;
	}

	@Override
	protected void configure() {
		bind(new TypeLiteral<Cache<String, String>>() { }).toProvider(CacheProvider.class);
		bind(DNodeStore.class).to(JBCNodeStore.class);
		bindBroadcastAddress();
		bindBindAddress();
		bindUserDomain();
		bindJBCConfig();
	}

	// --- PRIVATE METHODS --- //
	
	private void bindBindAddress() {
		String tmp = (bindAddress != null ? bindAddress.getHostAddress() : System.getProperty(BIND_ADDRESS_PROP));
		if(tmp != null) {
			bind(String.class).annotatedWith(Names.named("jbcBindAddress")).toInstance(tmp);
		}
	}
	
	private void bindJBCConfig() {
		String config = findConfig();
		bind(String.class).annotatedWith(Names.named("jbcConfig")).toInstance(config);
	}
	
	private String findConfig() {
		if(jbcConfig != null) {
			return jbcConfig;
		} else if (System.getProperty(FORCE_LOCAL_PROP) != null) {
			return DEF_LOCAL_CONFIG;
		} else {
			return System.getProperty(JBC_CONFIG_PROP, DEF_JBC_CONFIG);
		}
	}

	private void bindBroadcastAddress() {
		String address = findBroadcastAddress();
		bind(String.class).annotatedWith(Names.named("jbcBroadcastAddress")).toInstance(address);
	}

	private String findBroadcastAddress() {
		if(broadcastAddress != null) {
			return broadcastAddress.getHostAddress();
		} else {
			String tmp = System.getProperty(BROADCAST_PROP, DEF_BROADCAST_ADDRESS);
			try {
				return InetAddress.getByName(tmp).getHostAddress();
			} catch(UnknownHostException e) {
				log.error("Failed to create broadcast address '" + tmp + "'; Falling back to default value '" + DEF_BROADCAST_ADDRESS + "'");
				return DEF_BROADCAST_ADDRESS;
			}
		}
	}

	private void bindUserDomain() {
		String user = System.getProperty("user.name");
		bind(String.class).annotatedWith(Names.named("currentUser")).toInstance(user);
		String domain = findUserDomain(user);
		bind(String.class).annotatedWith(Names.named("userDomain")).toInstance(domain);
	}

	private String findUserDomain(String user) {
		if(userDomain != null) {
			return userDomain;
		} else {
			String domain = System.getProperty(USER_DOMAIN_PROP);
			if(domain == null) {
				domain = user;
			}
			return domain;
		}
	}
}
